﻿using System;
using System.ComponentModel;

namespace Taste
{
    /// <summary>
    /// Represents a state machine.
    /// </summary>
    /// <typeparam name="TState">An enum representing the states of the machine.</typeparam>
    /// <typeparam name="TTrigger">An enum representing the triggers handled by the states.</typeparam>
    public interface IStateMachine<TState, TTrigger> : INotifyPropertyChanged
        where TState : struct
        where TTrigger : struct
    {
        /// <summary>
        /// Defines a state.
        /// </summary>
        /// <param name="name">The name of the state.</param>
        /// <param name="onEntered">The action to perform when the state has been entered.</param>
        /// <param name="onLeaving">The action to perform when leaving the state.</param>
        void DefineState(TState name, Action<ICurrentState<TState, TTrigger>> onEntered, Action<IStateTransition<TState>> onLeaving);

        /// <summary>
        /// Returns the trigger corresponding to the given name.
        /// </summary>
        /// <param name="triggerName">The name of the trigger to return.</param>
        /// <returns>A trigger.</returns>
        ITrigger GetTrigger(TTrigger triggerName);

        /// <summary>
        /// Defines the default effect of the specified trigger, with an optional condition. 
        /// This effect is applied when the trigger and fired and the current state did not define an effect for this trigger.
        /// </summary>
        /// <param name="triggerName">The trigger name.</param>
        /// <param name="effect">The default effect, or null to reset the default effect.</param>
        /// <param name="condition">The optional condition for the default effect.</param>
        void OnFiredDefault(TTrigger triggerName, Action effect, Func<bool> condition = null);

        /// <summary>
        /// Defines the default effect of the specified trigger, with an optional condition. 
        /// This effect is applied when the trigger and fired and the current state did not define an effect for this trigger.
        /// </summary>
        /// <param name="triggerName">The trigger name.</param>
        /// <param name="effect">The default effect, or null to reset the default effect.</param>
        /// <param name="condition">The optional condition for the default effect.</param>
        void OnFiredDefault<TParam>(TTrigger triggerName, Action<TParam> effect, Func<TParam, bool> condition = null);

        /// <summary>
        /// Defines what to do when an error happens and the current state did not define a behavior to handle the error.
        /// </summary>
        /// <param name="action"></param>
        void OnErrorDefault(Action<IStateError<TState>> action);

        /// <summary>
        /// Starts the machine and enters the initial state.
        /// </summary>
        /// <param name="initialState">The initial state.</param>
        /// <param name="userData">The custom data to pass to the initial state.</param>
        void Start(TState initialState, object userData = null);

        /// <summary>
        /// The current state.
        /// </summary>
        ICurrentState<TState, TTrigger> CurrentState { get; }

        /// <summary>
        /// Raised when the current state has changed.
        /// </summary>
        event EventHandler CurrentStateChanged;
    }
}
